/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.apis.graphics.spritetext;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Paint;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.os.SystemClock;
import android.util.Log;

import com.example.android.apis.R;

public class SpriteTextRenderer implements GLSurfaceView.Renderer {

	public SpriteTextRenderer(Context context) {
		mContext = context;
		mTriangle = new Triangle();
		mProjector = new Projector();
		mLabelPaint = new Paint();
		mLabelPaint.setTextSize(32);
		mLabelPaint.setAntiAlias(true);
		mLabelPaint.setARGB(0xff, 0x00, 0x00, 0x00);
	}

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		/*
		 * By default, OpenGL enables features that improve quality but reduce
		 * performance. One might want to tweak that especially on software
		 * renderer.
		 */
		gl.glDisable(GL10.GL_DITHER);

		/*
		 * Some one-time OpenGL initialization can be made here probably based
		 * on features of this particular context
		 */
		gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

		gl.glClearColor(.5f, .5f, .5f, 1);
		gl.glShadeModel(GL10.GL_SMOOTH);
		gl.glEnable(GL10.GL_DEPTH_TEST);
		gl.glEnable(GL10.GL_TEXTURE_2D);

		/*
		 * Create our texture. This has to be done each time the surface is
		 * created.
		 */

		int[] textures = new int[1];
		gl.glGenTextures(1, textures, 0);

		mTextureID = textures[0];
		gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);

		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
				GL10.GL_NEAREST);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
				GL10.GL_LINEAR);

		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
				GL10.GL_CLAMP_TO_EDGE);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
				GL10.GL_CLAMP_TO_EDGE);

		gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
				GL10.GL_REPLACE);

		InputStream is = mContext.getResources().openRawResource(R.raw.robot);
		Bitmap bitmap;
		try {
			bitmap = BitmapFactory.decodeStream(is);
		} finally {
			try {
				is.close();
			} catch (IOException e) {
				// Ignore.
			}
		}

		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
		bitmap.recycle();

		if (mLabels != null) {
			mLabels.shutdown(gl);
		} else {
			mLabels = new LabelMaker(true, 256, 64);
		}
		mLabels.initialize(gl);
		mLabels.beginAdding(gl);
		mLabelA = mLabels.add(gl, "A", mLabelPaint);
		mLabelB = mLabels.add(gl, "B", mLabelPaint);
		mLabelC = mLabels.add(gl, "C", mLabelPaint);
		mLabelMsPF = mLabels.add(gl, "ms/f", mLabelPaint);
		mLabels.endAdding(gl);

		if (mNumericSprite != null) {
			mNumericSprite.shutdown(gl);
		} else {
			mNumericSprite = new NumericSprite();
		}
		mNumericSprite.initialize(gl, mLabelPaint);
	}

	public void onDrawFrame(GL10 gl) {
		/*
		 * By default, OpenGL enables features that improve quality but reduce
		 * performance. One might want to tweak that especially on software
		 * renderer.
		 */
		gl.glDisable(GL10.GL_DITHER);

		gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
				GL10.GL_MODULATE);

		/*
		 * Usually, the first thing one might want to do is to clear the screen.
		 * The most efficient way of doing this is to use glClear().
		 */

		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

		/*
		 * Now we're ready to draw some 3D objects
		 */

		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();

		GLU.gluLookAt(gl, 0.0f, 0.0f, -2.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

		gl.glActiveTexture(GL10.GL_TEXTURE0);
		gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
		gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
				GL10.GL_REPEAT);
		gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
				GL10.GL_REPEAT);

		if (false) {
			long time = SystemClock.uptimeMillis();
			if (mLastTime != 0) {
				long delta = time - mLastTime;
				Log.w("time", Long.toString(delta));
			}
			mLastTime = time;
		}

		long time = SystemClock.uptimeMillis() % 4000L;
		float angle = 0.090f * ((int) time);

		gl.glRotatef(angle, 0, 0, 1.0f);
		gl.glScalef(2.0f, 2.0f, 2.0f);

		mTriangle.draw(gl);

		mProjector.getCurrentModelView(gl);
		mLabels.beginDrawing(gl, mWidth, mHeight);
		drawLabel(gl, 0, mLabelA);
		drawLabel(gl, 1, mLabelB);
		drawLabel(gl, 2, mLabelC);
		float msPFX = mWidth - mLabels.getWidth(mLabelMsPF) - 1;
		mLabels.draw(gl, msPFX, 0, mLabelMsPF);
		mLabels.endDrawing(gl);

		drawMsPF(gl, msPFX);
	}

	private void drawMsPF(GL10 gl, float rightMargin) {
		long time = SystemClock.uptimeMillis();
		if (mStartTime == 0) {
			mStartTime = time;
		}
		if (mFrames++ == SAMPLE_PERIOD_FRAMES) {
			mFrames = 0;
			long delta = time - mStartTime;
			mStartTime = time;
			mMsPerFrame = (int) (delta * SAMPLE_FACTOR);
		}
		if (mMsPerFrame > 0) {
			mNumericSprite.setValue(mMsPerFrame);
			float numWidth = mNumericSprite.width();
			float x = rightMargin - numWidth;
			mNumericSprite.draw(gl, x, 0, mWidth, mHeight);
		}
	}

	private void drawLabel(GL10 gl, int triangleVertex, int labelId) {
		float x = mTriangle.getX(triangleVertex);
		float y = mTriangle.getY(triangleVertex);
		mScratch[0] = x;
		mScratch[1] = y;
		mScratch[2] = 0.0f;
		mScratch[3] = 1.0f;
		mProjector.project(mScratch, 0, mScratch, 4);
		float sx = mScratch[4];
		float sy = mScratch[5];
		float height = mLabels.getHeight(labelId);
		float width = mLabels.getWidth(labelId);
		float tx = sx - width * 0.5f;
		float ty = sy - height * 0.5f;
		mLabels.draw(gl, tx, ty, labelId);
	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {
		mWidth = w;
		mHeight = h;
		gl.glViewport(0, 0, w, h);
		mProjector.setCurrentView(0, 0, w, h);

		/*
		 * Set our projection matrix. This doesn't have to be done each time we
		 * draw, but usually a new projection needs to be set when the viewport
		 * is resized.
		 */

		float ratio = (float) w / h;
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
		mProjector.getCurrentProjection(gl);
	}

	private int mWidth;
	private int mHeight;
	private Context mContext;
	private Triangle mTriangle;
	private int mTextureID;
	private int mFrames;
	private int mMsPerFrame;
	private final static int SAMPLE_PERIOD_FRAMES = 12;
	private final static float SAMPLE_FACTOR = 1.0f / SAMPLE_PERIOD_FRAMES;
	private long mStartTime;
	private LabelMaker mLabels;
	private Paint mLabelPaint;
	private int mLabelA;
	private int mLabelB;
	private int mLabelC;
	private int mLabelMsPF;
	private Projector mProjector;
	private NumericSprite mNumericSprite;
	private float[] mScratch = new float[8];
	private long mLastTime;
}

class Triangle {
	public Triangle() {

		// Buffers to be passed to gl*Pointer() functions
		// must be direct, i.e., they must be placed on the
		// native heap where the garbage collector cannot
		// move them.
		//
		// Buffers with multi-byte datatypes (e.g., short, int, float)
		// must have their byte order set to native order

		ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
		vbb.order(ByteOrder.nativeOrder());
		mFVertexBuffer = vbb.asFloatBuffer();

		ByteBuffer tbb = ByteBuffer.allocateDirect(VERTS * 2 * 4);
		tbb.order(ByteOrder.nativeOrder());
		mTexBuffer = tbb.asFloatBuffer();

		ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
		ibb.order(ByteOrder.nativeOrder());
		mIndexBuffer = ibb.asShortBuffer();

		for (int i = 0; i < VERTS; i++) {
			for (int j = 0; j < 3; j++) {
				mFVertexBuffer.put(sCoords[i * 3 + j]);
			}
		}

		for (int i = 0; i < VERTS; i++) {
			for (int j = 0; j < 2; j++) {
				mTexBuffer.put(sCoords[i * 3 + j] * 2.0f + 0.5f);
			}
		}

		for (int i = 0; i < VERTS; i++) {
			mIndexBuffer.put((short) i);
		}

		mFVertexBuffer.position(0);
		mTexBuffer.position(0);
		mIndexBuffer.position(0);
	}

	public void draw(GL10 gl) {
		gl.glFrontFace(GL10.GL_CCW);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
		gl.glEnable(GL10.GL_TEXTURE_2D);
		gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexBuffer);
		gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, VERTS,
				GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
	}

	public float getX(int vertex) {
		return sCoords[3 * vertex];
	}

	public float getY(int vertex) {
		return sCoords[3 * vertex + 1];
	}

	private final static int VERTS = 3;

	private FloatBuffer mFVertexBuffer;
	private FloatBuffer mTexBuffer;
	private ShortBuffer mIndexBuffer;
	// A unit-sided equalateral triangle centered on the origin.
	private final static float[] sCoords = {
			// X, Y, Z
			-0.5f, -0.25f, 0, 0.5f, -0.25f, 0, 0.0f, 0.559016994f, 0 };
}
